#pragma once

#include <boost/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>




namespace boostext
{
	/**
	 * This is the first draft for a lock_ptr, as proposed by Andrei Alexandrescu
	 * on http://www.drdobbs.com/cpp/184403766;jsessionid=0SUFITSCIHFMJQE1GHRSKHWATMY32JVN.
	 */
	template
	<
		typename T,
		typename lock = boost::unique_lock<boost::mutex>
	>
	class lock_ptr
		: public boost::noncopyable
	{
	private:

		T*     m_object;
		lock   m_lock;

	public:

		// Constructors/destructors
		template <typename mutex>
		lock_ptr(volatile T& object, mutex& mtx)
			: m_object(const_cast<T*>(&object))
			, m_lock(mtx)
		{}

		// Pointer behavior
		T& operator*()
		{
			return *m_object;
		}

		T* operator->()
		{
			return m_object;
		}
	};
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * This is the first draft for a synchronized pointer that shall provide defined read/write
	 * access to objects amongst multiple threads. In order for this to work, the contained type
	 * must follow some rules:
	 *
	 * - Any const function SHALL NOT MODIFY ANYTHING (that is not synchronized already)
	 * - Any non-const function SHALL ONLY MODIFY THIS OBJECT (or anything that is synchronized as well)
	 *
	 * If the above is true, then the contained object is safe to use in multiple threads.
	 *
	 * Since the synchronized class makes use of reference counting for both the contained data and
	 * the mutex, it can be copied around without performing deep (slow) copies.
	 */
	template
	<
		typename T
	>
	class synchronized
	{
	public:

		typedef boost::shared_mutex         mutex;
		typedef boost::shared_lock<mutex>   read_lock;
		typedef boost::unique_lock<mutex>   write_lock;

		template <typename Y, typename lock>
		struct lock_ptr
		{
		private:

			boost::shared_ptr<Y>   m_data;
			lock                   m_lock;

		public:

			/**
			 * Creates a new lock_ptr by moving the lock from the old one to this one.
			 */
			lock_ptr(lock_ptr<Y, lock>& that)
				: m_data(that.m_data)
				, m_lock(that.m_lock.move())
			{}

			lock_ptr(boost::shared_ptr<T> data, boost::shared_ptr<mutex> m)
				: m_data(data)
				, m_lock(*m)
			{}



			// Pointer behavior
			Y& operator*()
			{
				return m_data.operator*();
			}

			Y* operator->()
			{
				return m_data.operator->();
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////

		typedef lock_ptr<const T, read_lock>   read_ptr;
		typedef lock_ptr<T, write_lock>        write_ptr;

	private:

		boost::shared_ptr<T>       m_data;
		boost::shared_ptr<mutex>   m_mutex;

	public:

		/**
		 * Creates an empty synchronized object.
		 */
		synchronized()
		{}

		/**
		 * Creates a synchronized object from another one that stores a different type.
		 * As long as both types are related (through inheritance) they can be
		 * converted.
		 */
		template <typename Y>
		synchronized(const synchronized<Y>& that)
			: m_data(that.m_data)
			, m_mutex(that.m_mutex)
		{}

		/**
		 * Creates a synchronized object from the given data.
		 * As long as the data is only accessed through this
		 * or copied-from instances of this object, then it
		 * is in a defined state, even when manipulated by
		 * multiple threads.
		 */
		synchronized(boost::shared_ptr<T> data)
			: m_data(data)
			, m_mutex(new mutex)
		{}



		/**
		 * Obtains a read_ptr from this object.
		 * The contained data can be used for reading only.
		 *
		 * @throws programming_error when the ptr points to NULL
		 */
		read_ptr read() const
		{
			verify(m_data);

			return read_ptr(m_data, m_mutex);
		}

		/**
		 * Obtains a write_ptr from this object.
		 * The contained data can be used for reading & writing.
		 *
		 * @throws programming_error when the ptr points to NULL
		 */
		write_ptr write()
		{
			verify(m_data);

			return write_ptr(m_data, m_mutex);
		}



		/**
		 * Tests if this ptr holds data.
		 */
		operator bool() const
		{
			return m_data;
		}
	};
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
